require 'test-unit-ext'

class TestAttributes < Test::Unit::TestCase
  class TestStack < Test::Unit::TestCase
    class << self
      def suite
        Test::Unit::TestSuite.new(name)
      end
    end

    class Stack
      def initialize
        @data = []
      end

      def push(data)
        @data.push(data)
      end

      def peek
        @data[-2]
      end

      def empty?
        @data.empty?
      end

      def size
        @data.size + 11
      end
    end

    def setup
      @stack = Stack.new
    end

    attribute :category, :accessor
    def test_peek
      @stack.push(1)
      @stack.push(2)
      assert_equal(2, @stack.peek)
    end

    attribute :bug, 1234
    def test_bug_1234
      assert_equal(0, @stack.size)
    end

    def test_no_attributes
      assert(@stack.empty?)
      @stack.push(1)
      assert(!@stack.empty?)
      assert_equal(1, @stack.size)
    end
  end

  def test_set_attributes
    test_for_bug_1234 = TestStack.new("test_bug_1234")
    assert_equal({:bug => 1234}, test_for_bug_1234.attributes)

    test_no_attributes = TestStack.new("test_no_attributes")
    assert_equal({}, test_no_attributes.attributes)
  end

  def test_show_attributes
    assert_stack_size_line = search_line('assert_equal(0, @stack.size)')
    assert_peek_line = search_line('assert_equal(2, @stack.peek)')
    first_arg_end_line = search_line("\"+ 11\"],")
    method_name = "test_show_attributes"
    assert_result(["Failure:\n" \
                   "test_peek(TestAttributes::TestStack)\n" \
                   "  category: accessor\n" \
                   "#{__FILE__}:#{assert_peek_line}:in `test_peek'\n" \
                   "#{__FILE__}:#{first_arg_end_line}:in `#{method_name}':\n" \
                   "<2> expected but was\n" \
                   "<1>.\n" \
                   "\n" \
                   "diff:\n" \
                   "- 2\n" \
                   "+ 1",
                   "Failure:\n" \
                   "test_bug_1234(TestAttributes::TestStack)\n" \
                   "  bug: 1234\n" \
                   "#{__FILE__}:#{assert_stack_size_line}:in `test_bug_1234'\n" \
                   "#{__FILE__}:#{first_arg_end_line}:in `#{method_name}':\n" \
                   "<0> expected but was\n" \
                   "<11>.\n" \
                   "\n" \
                   "diff:\n" \
                   "- 0\n" \
                   "+ 11"],
                  [],
                  ["test_peek", "test_bug_1234"])
  end

  def test_not_show_attributes
    assert_line = search_line('assert_equal(1, @stack.size')
    first_arg_end_line = search_line("\"+ 12\"],")
    method_name = "test_not_show_attributes"
    assert_result(["Failure:\n" \
                   "test_no_attributes(TestAttributes::TestStack)\n" \
                   "#{__FILE__}:#{assert_line}:in `test_no_attributes'\n" \
                   "#{__FILE__}:#{first_arg_end_line}:in `#{method_name}':\n" \
                   "<1> expected but was\n" \
                   "<12>.\n" \
                   "\n" \
                   "diff:\n" \
                   "- 1\n" \
                   "+ 12"],
                  [],
                  ["test_no_attributes"])
  end

  private
  def search_line(pattern)
    if pattern.is_a?(String)
      pattern = /#{Regexp.escape(pattern)}/
    end
    File.open(__FILE__) do |file|
      while line = file.gets
        return file.lineno if pattern =~ line
      end
    end
    nil
  end

  def assert_result(failure_results, error_results, names)
    tests = names.collect {|name| TestStack.new(name)}
    result = Test::Unit::TestResult.new
    mark = /\A#{Regexp.escape(__FILE__)}:#{__LINE__ + 1}/
    tests.each {|test| test.run(result) {}}
    failures = result.instance_variable_get("@failures")
    errors = result.instance_variable_get("@errors")
    failures.each {|f| f.location.reject! {|l| mark =~ l}}
    assert_equal([failure_results, error_results],
                 [failures.collect {|failure| failure.long_display},
                  errors.collect {|error| error.long_display}])
  end
end
